﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ads
{
    public class Receiver : Context
    {
        private bool running = true;
        private Socket sck = null;
        private ReceiveCallback callback = null;          
        private TcpClient tcpclient = null;
        private Stream stream = null;

        public Thread thread = null;

        public Receiver(TcpClient tcpClient, ReceiveCallback callback)
        {
            this.stream = tcpClient.GetStream();
            this.callback = callback;
        }

        public void start()
        {
            thread = new Thread(new ThreadStart(this.run));
            thread.Start();
            running = true;
        }

        private int findMagic(byte[] data, int sz)
        {
            byte v1, v2, v3, v4;
            v1 = v2 = v3 = v4 = 0;
            for (int i = 0; i < sz; i += 1)
            {
                v1 = v2;
                v2 = v3;
                v3 = v4;
                v4 = data[i];

                if (v1 == 0x20 && v2 == 0x14 && v3 == 0x01 && v4 == 0x06)
                {
                    return i - 3;
                }
            }
            return -1;
        }

        private bool getOneFullPackage(ref DataStorage large, ref byte [] ret)
        {
            int index = findMagic(large.buffer, large.index + 1);
            if (index == -1)
            {
                large.index = 0;
                return false;
            }

            // read header
            byte[] bMagic = new byte[4];
            byte[] bSize = new byte[4];
            byte[] bType = new byte[4];

            Buffer.BlockCopy(large.buffer, index, bMagic, 0, bMagic.Length);
            Buffer.BlockCopy(large.buffer, index + bMagic.Length, bSize, 0, bSize.Length);
            Buffer.BlockCopy(large.buffer, index + bMagic.Length + bSize.Length, bType, 0, bType.Length);

            int magic = Msg.ntohl(bMagic);
            int size = Msg.ntohl(bSize);
            int type = Msg.ntohl(bType);

            int available = large.index + 1 - index;
            if (available < size)
            {
                // We still have not enough buffer.
                return false;
            }

            if (ret == null || ret.Length != size + 12)
            {
                ret = new byte[size + 12];
            }
            Buffer.BlockCopy(large.buffer, index, ret, 0, size + 12);

            // Move the back part to the head
            int srcIndex = index + size + 12;
            int cpySize = large.index - srcIndex;
            if (cpySize > 0)
            {
                Buffer.BlockCopy(large.buffer, srcIndex, large.buffer, 0, cpySize);
            }
            large.index = cpySize;
            return true;
        }

        private class DataStorage
        {
            public byte [] buffer = new byte[65535 * 10];
            public int index = 0;
        }

        public void run()
        {
            DataStorage large = new DataStorage();
            byte[] buffer = new byte[65535 * 2];
            byte[] packet = null;

            Msg msg = new Msg();

            while (running)
            {
                try
                {
                    int sz = stream.Read(buffer, 0, buffer.Length);
                    if (sz <= 0)
                    {
                        running = false;
                        continue;
                    }

                    Buffer.BlockCopy(buffer, 0, large.buffer, large.index, sz);
                    large.index += sz;

                    while (getOneFullPackage(ref large, ref packet))
                    {
                        msg.fromByteArray(packet);
                        callback.onReceive(this, msg);
                    }
                }
                catch (SocketException se) 
                {
                    running = false;
                }
            }
        }

        public void run2()
        {
            byte[] large1 = new byte[65535 * 10];
            byte[] large2 = new byte[65535 * 10];
            byte[] large = large2;

            byte[] buffer = new byte[65535 * 2];

            Msg msg = new Msg();
            int sizeNeeded = 0;
            
            while (running)
            {
                try
                {
                    int sz = stream.Read(buffer, 0, buffer.Length);
                    if (sz > 12)
                    {
                        byte v1, v2, v3, v4;
                        v1 = v2 = v3 = v4 = 0;
                        for (int i = 0; i < sz; i += 1)
                        {
                            sizeNeeded -= 1;
                            large[i] = buffer[i];
                            v1 = v2;
                            v2 = v3;
                            v3 = v4;
                            v4 = buffer[i];

                            if (sizeNeeded == 0)
                            {
                                msg.fromByteArray(large);
                                callback.onReceive(this, msg);
                                sizeNeeded = -1;
                            }

                            if (v1 == 0x20 && v2 == 0x14 && v3 == 0x01 && v4 == 0x06)
                            {
                                byte[] bSize = new byte[4];
                                Buffer.BlockCopy(buffer, i + 1, bSize, 0, bSize.Length);
                                sizeNeeded = Msg.ntohl(bSize) + 8;

                                large = large == large1 ? large2 : large1;
                                large[0] = v1;
                                large[1] = v2;
                                large[2] = v3;
                                large[3] = v4;
                            }
                        }
                        if (sizeNeeded == 0)
                        {
                            msg.fromByteArray(large);
                            callback.onReceive(this, msg);
                        }
                        else if (sizeNeeded < 0)
                        {
                            Console.WriteLine("error (< 0)");
                        }
                    }
                    else
                    {
                        running = false;
                    }
                } catch (SocketException se) { }
            }
        }

        public void send(Msg msg)
        {
            byte[] buffer = msg.packet.toByteArray();
            stream.Write(buffer, 0, buffer.Length);
            stream.Flush();
        }

        public void shutdown()
        {
            this.running = false;
            try {
                if (sck != null)
                    sck.Close();
                if (tcpclient != null)
                    tcpclient.Close();
            } catch (SocketException se) { }
        }
    }
}
